【個人的には神ツール】AwsOrganizationFormation(OSS)でAWS Organizationsをコードで管理する
中山(順)です
「AWS Organizationsをコードで管理したい・・・」
そんなことを思ったことはありませんか?
今日はAwsOrganizationFormationというOSSのご紹介です。
READMEには以下のように記載されています。
AWS Organization Formation is an Infrastructure as Code (IaC) tool for AWS Organizations.
OlafConijn/AwsOrganizationFormation
AWS Organizationをコードで管理するツールのようです。 これは俺得。
AwsOrganizationFormationの機能
主要な機能として、以下の3つが挙げられています。
- Infrastructure as Code for AWS Organizations(AWS Organizations自体をコード化)
- CloudFormation annotations to provision resources cross account(アカウントを横断してリソースをプロビジョニングするためのアノテーション)
- Automation of account creation and resource provisioning(アカウントの作成とリソースのプロビジョニングを自動化)
なんと、Organizationsのコード化だけでなくアカウント横断のリソースのプロビジョニングまでできるようです。 ますます俺得!
やってみた
- 事前準備
- 既存のOrganizationをコード化
- 子アカウントの作成
- OU/SCP/Password Policyの管理
- 子アカウントにプロビジョニングするための準備(テンプレートの作成)
- 各アカウントでプロビジョニングされるスタックのテンプレートを確認
- 子アカウントへのリソースのプロビジョニング
事前準備
まず、AwsOrganizationFormationをインストールします。
sudo npm install -g aws-organization-formation
提供されているコマンドは以下の通りです。
$ org-formation --help
Usage: org-formation [options] [command] aws organization formation Options: -v, --version output the version number -h, --help output usage information Commands: create-change-set [options] <templateFile> create change set that can be reviewed and executed later delete-stacks [options] removes all stacks deployed to accounts using org-formation describe-stacks [options] list all stacks deployed to accounts using org-formation execute-change-set [options] <change-set-name> execute previously created change set init-pipeline [options] initializes organization and created codecommit repo, codebuild and codepipeline init [options] <file> generate template & initialize organization perform-tasks [options] <tasks-file> performs all tasks from either a file or directory structure print-stacks [options] <templateFile> outputs cloudformation templates generated by org-formation to the console update [options] <templateFile> update organization resources update-stacks [options] <templateFile> update cloudformation resources in accounts validate-stacks [options] <templateFile> validates the cloudformation templates that will be generated validate-tasks [options] <templateFile> Will validate the tasks file, including configured tasks
AwsOrganizationFormationを介してAWSにアクセスするため、マスターアカウントのアクセスキーを設定しておく必要があります。 これ以降、AwsOrganizationFormationのコマンドを実行する際には以下のプロファイルを指定します。
aws configure --profile organizations
既存のOrganizationをコード化
検証を開始する時点で、親アカウントとAWS Organizationsを通して作成した子アカウント1つが存在する状態です。
まず、initコマンドで現在の状態をコードとして出力します。
org-formation init organization.yml --region us-east-1 --profile organizations
INFO: Your organization template is written to organization.yml INFO: Hope this will get you started! INFO: INFO: You can keep the organization.yml file on disk or even better, under source control. INFO: If you work with code pipeline you might find init-pipeline an interesting command too. INFO: INFO: Dont worry about losing the organization.yml file, at any point you can recreate it. INFO: Have fun! INFO: INFO: --OC
init-pipelineコマンドでパイプラインごと作成することもできるようですが、それはまたの機会に・・・
出力されたコードを確認してみます。
cat organization.yml
AWSTemplateFormatVersion: '2010-09-09-OC' Description: default template generated for organization with master account XXXXXXXXXXXX Organization: MasterAccount: Type: OC::ORG::MasterAccount Properties: AccountName: Master AccountId: 'XXXXXXXXXXXX' OrganizationRoot: Type: OC::ORG::OrganizationRoot Properties: NobuhiroNakayamaAccount: Type: OC::ORG::Account Properties: AccountName: Nobuhiro Nakayama AccountId: 'YYYYYYYYYYYY' RootEmail: ********+001@gmail.com
なお、initコマンドを実行すると自動で状態ファイルの生成とS3バケットへの保存が行われます。 S3バケットがない場合にはバケットも自動で作成されます。 このS3バケットは"--region"で指定したリージョンに作成されます。
{ "masterAccountId": "XXXXXXXXXXXX", "bindings": { "OC::ORG::MasterAccount": { "MasterAccount": { "type": "OC::ORG::MasterAccount", "logicalId": "MasterAccount", "physicalId": "XXXXXXXXXXXX", "lastCommittedHash": "d84c19fee925ab85000bb5c4012b4b85" } }, "OC::ORG::OrganizationRoot": { "OrganizationRoot": { "type": "OC::ORG::OrganizationRoot", "logicalId": "OrganizationRoot", "physicalId": "r-i18d", "lastCommittedHash": "6be66ccf6b2a5417439fec93c294f165" } }, "OC::ORG::Account": { "NobuhiroNakayamaAccount": { "type": "OC::ORG::Account", "logicalId": "NobuhiroNakayamaAccount", "physicalId": "YYYYYYYYYYYY", "lastCommittedHash": "dab47fd962b4aade7fc51a7b5afbb711" } } }, "stacks": {}, "values": {}, "previousTemplate": "{\"AWSTemplateFormatVersion\":\"2010-09-09-OC\",\"Description\":\"default template generated for organization with master account XXXXXXXXXXXX\",\"Organization\":{\"MasterAccount\":{\"Type\":\"OC::ORG::MasterAccount\",\"Properties\":{\"AccountName\":\"Master\",\"AccountId\":\"XXXXXXXXXXXX\"}},\"OrganizationRoot\":{\"Type\":\"OC::ORG::OrganizationRoot\",\"Properties\":null},\"NobuhiroNakayamaAccount\":{\"Type\":\"OC::ORG::Account\",\"Properties\":{\"AccountName\":\"Nobuhiro Nakayama\",\"AccountId\":\"YYYYYYYYYYYY\",\"RootEmail\":\"********+001@gmail.com\"}}}}" }
子アカウントの作成
テンプレートを修正します。
AWSTemplateFormatVersion: '2010-09-09-OC' Description: default template generated for organization with master account XXXXXXXXXXXX Organization: MasterAccount: Type: OC::ORG::MasterAccount Properties: AccountName: Master AccountId: 'XXXXXXXXXXXX' OrganizationRoot: Type: OC::ORG::OrganizationRoot Properties: NobuhiroNakayamaAccount: Type: OC::ORG::Account Properties: AccountName: Nobuhiro Nakayama AccountId: 'YYYYYYYYYYYY' RootEmail: ********+001@gmail.com MemberAccount: Type: OC::ORG::Account Properties: AccountName: OrgFormationTest RootEmail: ********+orgformation@gmail.com
Change Setを作成します。
$ org-formation create-change-set organization.yml --profile organizations
{ "changeSetName": "5f0417cb-9fe4-4255-98ea-d30ae89992f5", "changes": [ { "logicalId": "MemberAccount", "type": "OC::ORG::Account", "action": "Create" } ] }
execute-change-setコマンドで変更を適用します。
org-formation execute-change-set 5f0417cb-9fe4-4255-98ea-d30ae89992f5 --profile organizations
OC::ORG::Account | MemberAccount | Create (ZZZZZZZZZZZZ) OC::ORG::Account | MemberAccount | CommitHash INFO: done
AWS CLIでアカウントが作成されたことを確認します。
$ aws organizations list-accounts --profile organizations --query "sort_by(Accounts, &JoinedTimestamp)[-1]"
$ aws organizations list-accounts --profile organizations --query "sort_by(Accounts, &JoinedTimestamp)[-1]" { "Status": "ACTIVE", "Name": "OrgFormationTest", "Email": "********+orgformation@gmail.com", "JoinedMethod": "CREATED", "JoinedTimestamp": 1583569462.768, "Id": "ZZZZZZZZZZZZ", "Arn": "arn:aws:organizations::XXXXXXXXXXXX:account/o-xxxxxxxxxx/ZZZZZZZZZZZZ" }
OU/SCP/Password Policyの管理
そもそも何を管理できるかドキュメントで確認してみましょう。
https://github.com/OlafConijn/AwsOrganizationFormation/blob/master/docs/organization-resources.md
- MasterAccount
- Account
- OrganizationRoot
- OrganizationalUnit
- ServiceControlPolicy
- PasswordPolicy
最初に生成されたリソース以外に、"OrganizationalUnit", "ServiceControlPolicy", "PasswordPolicy" の3つのリソースをサポートしているようです。 以下のようにテンプレートを修正してみます。 ドキュメントの例をほぼそのまま拝借しました。 ちなみに、いろいろやり直したりしたため、アカウントのリソースIDやプロパティが少し変わっていますのでご了承くださいw
AWSTemplateFormatVersion: '2010-09-09-OC' Description: default template generated for organization with master account XXXXXXXXXXXX Organization: MasterAccount: Type: OC::ORG::MasterAccount Properties: AccountName: Master AccountId: 'XXXXXXXXXXXX' PasswordPolicy: !Ref PasswordPolicy OrganizationRoot: Type: OC::ORG::OrganizationRoot Properties: OrgFormationTestAccount: Type: OC::ORG::Account Properties: AccountName: OrgFormationTest AccountId: 'ZZZZZZZZZZZZ' RootEmail: ********+orgformation@gmail.com PasswordPolicy: !Ref PasswordPolicy NobuhiroNakayamaAccount: Type: OC::ORG::Account Properties: AccountName: Nobuhiro Nakayama AccountId: 'YYYYYYYYYYYY' RootEmail: ********+001@gmail.com PasswordPolicy: !Ref PasswordPolicy ProductionOU: Type: OC::ORG::OrganizationalUnit Properties: OrganizationalUnitName: production ServiceControlPolicies: !Ref RestrictUnusedRegionsSCP Accounts: !Ref OrgFormationTestAccount DevelopmentOU: Type: OC::ORG::OrganizationalUnit Properties: OrganizationalUnitName: development ServiceControlPolicies: !Ref RestrictUnusedRegionsSCP Accounts: !Ref NobuhiroNakayamaAccount RestrictUnusedRegionsSCP: Type: OC::ORG::ServiceControlPolicy Properties: PolicyName: RestrictUnusedRegions Description: Restrict Unused regions PolicyDocument: Version: '2012-10-17' Statement: - Sid: DenyUnsupportedRegions Effect: Deny NotAction: - 'cloudfront:*' - 'iam:*' - 'route53:*' - 'support:*' Resource: '*' Condition: StringNotEquals: 'aws:RequestedRegion': - us-east-1 - us-weat-2 - ap-northeast-1 PasswordPolicy: Type: OC::ORG::PasswordPolicy Properties: MaxPasswordAge: 30 MinimumPasswordLength: 12 RequireLowercaseCharacters: true RequireNumbers: true RequireSymbols: true RequireUppercaseCharacters: true PasswordReusePrevention: 5 AllowUsersToChangePassword: true
変更を適用します。 今度はChange Setを作成せずにorg-formation updateコマンドを利用しています。
org-formation update organization.yml --profile organizations
OC::ORG::ServiceControlPolicy | RestrictUnusedRegionsSCP | Create (p-8w6roxp1) OC::ORG::Account | OrgFormationTestAccount | Update OC::ORG::Account | OrgFormationTestAccount | CommitHash OC::ORG::Account | NobuhiroNakayamaAccount | Update OC::ORG::Account | NobuhiroNakayamaAccount | CommitHash OC::ORG::OrganizationalUnit | ProductionOU | Create (ou-i18d-cmtwak1n) OC::ORG::OrganizationalUnit | ProductionOU | Attach Policy (RestrictUnusedRegionsSCP) OC::ORG::OrganizationalUnit | ProductionOU | Attach Account (OrgFormationTestAccount) OC::ORG::OrganizationalUnit | ProductionOU | CommitHash OC::ORG::OrganizationalUnit | DevelopmentOU | Create (ou-i18d-md5cpw1c) OC::ORG::OrganizationalUnit | DevelopmentOU | Attach Policy (RestrictUnusedRegionsSCP) OC::ORG::OrganizationalUnit | DevelopmentOU | Attach Account (NobuhiroNakayamaAccount) OC::ORG::OrganizationalUnit | DevelopmentOU | CommitHash OC::ORG::MasterAccount | MasterAccount | Update OC::ORG::MasterAccount | MasterAccount | CommitHash INFO: done
詳細は割愛しますが、設定が反映されていることを確認しました。
子アカウントにプロビジョニングするための準備(テンプレートの作成)
org-formationでは、組織内のアカウントに対してリソースをまとめて展開することができます。 例えば、CloudTrailの設定を全てのアカウントに設定したり、ログを特定のバケットに集約することが可能です。
以下のサンプルファイルを使って使い方を確認していきます。
https://github.com/OlafConijn/AwsOrganizationFormation/blob/master/examples/templates/cloudtrail.yml
サンプルテンプレートを現行のOrganizationに併せて修正
まず、修正したものは以下の通りです。
AWSTemplateFormatVersion: '2010-09-09-OC' # Include file that contains Organization Section. # The Organization Section describes Accounts, Organizational Units, etc. Organization: !Include ../organization.yml # Any Binding that does not explicitly specify a region will default to this. # Value can be either string or list DefaultOrganizationBindingRegion: ap-northeast-1 # Section that contains a named list of Bindings. # Bindings determine what resources are deployed where # These bindings can be !Ref'd from the Resources in the resource section OrganizationBindings: # Binding for: S3Bucket, S3BucketPolicy CloudTrailBucketBinding: Account: !Ref MasterAccount # Binding for: CloudTrail, CloudTrailLogGroup, CloudTrailLogGroupRole CloudTrailBinding: Account: '*' IncludeMasterAccount: true Resources: CloudTrailS3Bucket: OrganizationBinding: !Ref CloudTrailBucketBinding DeletionPolicy: Retain Type: AWS::S3::Bucket Properties: BucketName: !Sub 'cloudtrail-${MasterAccount}' CloudTrailS3BucketPolicy: OrganizationBinding: !Ref CloudTrailBucketBinding Type: AWS::S3::BucketPolicy DependsOn: CloudTrailS3Bucket Properties: Bucket: !Ref CloudTrailS3Bucket PolicyDocument: Version: '2012-10-17' Statement: - Sid: 'AWSCloudTrailAclCheck' Effect: 'Allow' Principal: { Service: 'cloudtrail.amazonaws.com' } Action: 's3:GetBucketAcl' Resource: !Sub 'arn:aws:s3:::${CloudTrailS3Bucket}' - Sid: 'AWSCloudTrailWrite' Effect: 'Allow' Principal: { Service: 'cloudtrail.amazonaws.com' } Action: 's3:PutObject' Resource: !Sub 'arn:aws:s3:::${CloudTrailS3Bucket}/AWSLogs/*/*' Condition: StringEquals: s3:x-amz-acl: 'bucket-owner-full-control' CloudTrailLogGroup: OrganizationBinding: !Ref CloudTrailBinding Type: 'AWS::Logs::LogGroup' Properties: RetentionInDays: 14 LogGroupName: CloudTrail/audit-log CloudTrailLogGroupRole: OrganizationBinding: !Ref CloudTrailBinding Type: 'AWS::IAM::Role' Properties: RoleName: AWSCloudTrailLogGroupRole AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Sid: AssumeRole1 Effect: Allow Principal: Service: 'cloudtrail.amazonaws.com' Action: 'sts:AssumeRole' Policies: - PolicyName: 'cloudtrail-policy' PolicyDocument: Version: '2012-10-17' Statement: - Sid: AWSCloudTrailCreateLogStream Effect: Allow Action: - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: !GetAtt 'CloudTrailLogGroup.Arn' CloudTrail: OrganizationBinding: !Ref CloudTrailBinding Type: AWS::CloudTrail::Trail DependsOn: - CloudTrailS3BucketPolicy - CloudTrailLogGroup - CloudTrailLogGroupRole Properties: S3BucketName: !Ref CloudTrailS3Bucket IsLogging: false IncludeGlobalServiceEvents: true IsMultiRegionTrail: true CloudWatchLogsLogGroupArn: !GetAtt 'CloudTrailLogGroup.Arn' CloudWatchLogsRoleArn: !GetAtt 'CloudTrailLogGroupRole.Arn'
ここでポイントなのは、各リソースの属性として設定してある"OrganizationBinding"です。
CloudTrailをマルチアカウント環境で利用する場合、「あるアカウントにログを集約するためのS3バケットを作成」「全てのアカウントでCloudTrailを有効にしてログ集約用のS3バケットにログを保存」といった設計をよくやります。 上述のテンプレートでは、"AWS::S3::Bucket", "AWS::S3::BucketPolicy", "AWS::Logs::LogGroup", "AWS::IAM::Role", "AWS::CloudTrail::Trail"のリソースが1つのテンプレート上に記述されています。 このうち、"AWS::S3::Bucket", "AWS::S3::BucketPolicy"は、どこか一つのアカウントに作成したいのですが、これをOrganizationBindingで制御しています。
前半のOrganizationBindings > CloudTrailBucketBindingでログ集約用のS3バケットを作成するアカウントを定義しています。 また、OrganizationBindings > CloudTrailBindingでCloudTrailを有効にするアカウントを定義しており、ここではマスターアカウントを含むOrganization内の全てのアカウントが指定されています。
(OrganizationおよびDefaultOrganizationBindingRegionの説明は割愛します)
テンプレートを管理するディレクトリを作成し、上記のテンプレートを配置します。
mkdir templates cd templates/
各アカウントでプロビジョニングされるスタックのテンプレートを確認
print-stacksコマンドで各アカウントにプロビジョニングされるスタックのテンプレートを確認することができます。 意図した内容のテンプレートが生成されていることが確認できます。
org-formation print-stacks cloudtrail.yml --profile organizations
template for account XXXXXXXXXXXX and region ap-northeast-1 { "AWSTemplateFormatVersion": "2010-09-09", "Parameters": {}, "Resources": { "CloudTrailS3Bucket": { "DeletionPolicy": "Retain", "Type": "AWS::S3::Bucket", "Properties": { "BucketName": "cloudtrail-XXXXXXXXXXXX" } }, "CloudTrailS3BucketPolicy": { "Type": "AWS::S3::BucketPolicy", "DependsOn": "CloudTrailS3Bucket", "Properties": { "Bucket": { "Ref": "CloudTrailS3Bucket" }, "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "AWSCloudTrailAclCheck", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "s3:GetBucketAcl", "Resource": { "Fn::Sub": "arn:aws:s3:::${CloudTrailS3Bucket}" } }, { "Sid": "AWSCloudTrailWrite", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "s3:PutObject", "Resource": { "Fn::Sub": "arn:aws:s3:::${CloudTrailS3Bucket}/AWSLogs/*/*" }, "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } } ] } } }, "CloudTrailLogGroup": { "Type": "AWS::Logs::LogGroup", "Properties": { "RetentionInDays": 14, "LogGroupName": "CloudTrail/audit-log" } }, "CloudTrailLogGroupRole": { "Type": "AWS::IAM::Role", "Properties": { "RoleName": "AWSCloudTrailLogGroupRole", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "AssumeRole1", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Policies": [ { "PolicyName": "cloudtrail-policy", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "AWSCloudTrailCreateLogStream", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": { "Fn::GetAtt": [ "CloudTrailLogGroup", "Arn" ] } } ] } } ] } }, "CloudTrail": { "Type": "AWS::CloudTrail::Trail", "DependsOn": [ "CloudTrailS3BucketPolicy", "CloudTrailLogGroup", "CloudTrailLogGroupRole" ], "Properties": { "S3BucketName": { "Ref": "CloudTrailS3Bucket" }, "IsLogging": false, "IncludeGlobalServiceEvents": true, "IsMultiRegionTrail": true, "CloudWatchLogsLogGroupArn": { "Fn::GetAtt": [ "CloudTrailLogGroup", "Arn" ] }, "CloudWatchLogsRoleArn": { "Fn::GetAtt": [ "CloudTrailLogGroupRole", "Arn" ] } } } }, "Outputs": { "printDashCloudTrailS3Bucket": { "Value": { "Ref": "CloudTrailS3Bucket" }, "Description": "Cross Account dependency", "Export": { "Name": "print-CloudTrailS3Bucket" } } } } template for account YYYYYYYYYYYY and region ap-northeast-1 { "AWSTemplateFormatVersion": "2010-09-09", "Parameters": { "CloudTrailS3Bucket": { "Description": "Cross Account dependency", "Type": "String", "ExportAccountId": "XXXXXXXXXXXX", "ExportRegion": "ap-northeast-1", "ExportName": "print-CloudTrailS3Bucket" } }, "Resources": { "CloudTrailLogGroup": { "Type": "AWS::Logs::LogGroup", "Properties": { "RetentionInDays": 14, "LogGroupName": "CloudTrail/audit-log" } }, "CloudTrailLogGroupRole": { "Type": "AWS::IAM::Role", "Properties": { "RoleName": "AWSCloudTrailLogGroupRole", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "AssumeRole1", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Policies": [ { "PolicyName": "cloudtrail-policy", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "AWSCloudTrailCreateLogStream", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": { "Fn::GetAtt": [ "CloudTrailLogGroup", "Arn" ] } } ] } } ] } }, "CloudTrail": { "Type": "AWS::CloudTrail::Trail", "DependsOn": [ "CloudTrailLogGroup", "CloudTrailLogGroupRole" ], "Properties": { "S3BucketName": { "Ref": "CloudTrailS3Bucket" }, "IsLogging": false, "IncludeGlobalServiceEvents": true, "IsMultiRegionTrail": true, "CloudWatchLogsLogGroupArn": { "Fn::GetAtt": [ "CloudTrailLogGroup", "Arn" ] }, "CloudWatchLogsRoleArn": { "Fn::GetAtt": [ "CloudTrailLogGroupRole", "Arn" ] } } } }, "Outputs": {} } template for account ZZZZZZZZZZZZ and region ap-northeast-1 { "AWSTemplateFormatVersion": "2010-09-09", "Parameters": { "CloudTrailS3Bucket": { "Description": "Cross Account dependency", "Type": "String", "ExportAccountId": "XXXXXXXXXXXX", "ExportRegion": "ap-northeast-1", "ExportName": "print-CloudTrailS3Bucket" } }, "Resources": { "CloudTrailLogGroup": { "Type": "AWS::Logs::LogGroup", "Properties": { "RetentionInDays": 14, "LogGroupName": "CloudTrail/audit-log" } }, "CloudTrailLogGroupRole": { "Type": "AWS::IAM::Role", "Properties": { "RoleName": "AWSCloudTrailLogGroupRole", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "AssumeRole1", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "Policies": [ { "PolicyName": "cloudtrail-policy", "PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Sid": "AWSCloudTrailCreateLogStream", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": { "Fn::GetAtt": [ "CloudTrailLogGroup", "Arn" ] } } ] } } ] } }, "CloudTrail": { "Type": "AWS::CloudTrail::Trail", "DependsOn": [ "CloudTrailLogGroup", "CloudTrailLogGroupRole" ], "Properties": { "S3BucketName": { "Ref": "CloudTrailS3Bucket" }, "IsLogging": false, "IncludeGlobalServiceEvents": true, "IsMultiRegionTrail": true, "CloudWatchLogsLogGroupArn": { "Fn::GetAtt": [ "CloudTrailLogGroup", "Arn" ] }, "CloudWatchLogsRoleArn": { "Fn::GetAtt": [ "CloudTrailLogGroupRole", "Arn" ] } } } }, "Outputs": {} }
子アカウントへのリソースのプロビジョニング
validate-stacksコマンドでテンプレートを検証します。
org-formation validate-stacks cloudtrail.yml --profile organizations
INFO: template for stack validation account XXXXXXXXXXXX/ap-northeast-1 valid. INFO: template for stack validation account YYYYYYYYYYYY/ap-northeast-1 valid. INFO: template for stack validation account ZZZZZZZZZZZZ/ap-northeast-1 valid. INFO: done
update-stacksコマンドでテンプレートを利用してスタックをデプロイします。 初回作成時もこのコマンドです。
org-formation update-stacks cloudtrail.yml --stack-name cloudtrail --profile organizations
INFO: stack cloudtrail successfully updated in XXXXXXXXXXXX/ap-northeast-1. INFO: stack cloudtrail successfully updated in YYYYYYYYYYYY/ap-northeast-1. INFO: stack cloudtrail successfully updated in ZZZZZZZZZZZZ/ap-northeast-1. INFO: done
describe-stacksコマンドで作成されたスタックを確認します。
org-formation describe-stacks --profile organizations
{ "cloudtrail": [ { "accountId": "XXXXXXXXXXXX", "region": "ap-northeast-1", "stackName": "cloudtrail", "lastCommittedHash": "99ca4f9d5e7c3c14b5b93625b3d4f838", "logicalAccountId": "MasterAccount", "terminationProtection": false }, { "accountId": "ZZZZZZZZZZZZ", "region": "ap-northeast-1", "stackName": "cloudtrail", "lastCommittedHash": "99ca4f9d5e7c3c14b5b93625b3d4f838", "logicalAccountId": "OrgFormationTestAccount", "terminationProtection": false }, { "accountId": "YYYYYYYYYYYY", "region": "ap-northeast-1", "stackName": "cloudtrail", "lastCommittedHash": "99ca4f9d5e7c3c14b5b93625b3d4f838", "logicalAccountId": "NobuhiroNakayamaAccount", "terminationProtection": false } ] }
AWS CLIで各アカウントのスタックに含まれるリソースを確認してみます。
まず、集約用のS3バケットを作成するとしたアカウントのスタックです。
aws cloudformation describe-stack-resources \ --stack-name cloudtrail
{ "StackResources": [ { "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, "ResourceType": "AWS::CloudTrail::Trail", "Timestamp": "2020-03-08T13:58:03.042Z", "StackName": "cloudtrail", "PhysicalResourceId": "cloudtrail-CloudTrail-3NCR3OF874M7", "LogicalResourceId": "CloudTrail" }, { "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, "ResourceType": "AWS::Logs::LogGroup", "Timestamp": "2020-03-08T13:57:33.866Z", "StackName": "cloudtrail", "PhysicalResourceId": "CloudTrail/audit-log", "LogicalResourceId": "CloudTrailLogGroup" }, { "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, "ResourceType": "AWS::IAM::Role", "Timestamp": "2020-03-08T13:57:52.160Z", "StackName": "cloudtrail", "PhysicalResourceId": "AWSCloudTrailLogGroupRole", "LogicalResourceId": "CloudTrailLogGroupRole" }, { "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, "ResourceType": "AWS::S3::Bucket", "Timestamp": "2020-03-08T13:57:55.497Z", "StackName": "cloudtrail", "PhysicalResourceId": "cloudtrail-XXXXXXXXXXXX", "LogicalResourceId": "CloudTrailS3Bucket" }, { "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/cloudtrail/bfad7db0-6144-11ea-9ad5-0e7389934c04", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, "ResourceType": "AWS::S3::BucketPolicy", "Timestamp": "2020-03-08T13:57:58.925Z", "StackName": "cloudtrail", "PhysicalResourceId": "cloudtrail-CloudTrailS3BucketPolicy-BK6YXBTJQJ8B", "LogicalResourceId": "CloudTrailS3BucketPolicy" } ] }
他のアカウントのスタックは以下のようになっております。 S3バケットおよびバケットポリシーが含まれていないことを確認できます。
aws cloudformation describe-stack-resources \ --stack-name cloudtrail \ --profile organizations-member
{ "StackResources": [ { "StackId": "arn:aws:cloudformation:ap-northeast-1:YYYYYYYYYYYY:stack/cloudtrail/e8cd9900-6144-11ea-800c-0e8a9cd56576", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, "ResourceType": "AWS::CloudTrail::Trail", "Timestamp": "2020-03-08T13:59:05.432Z", "StackName": "cloudtrail", "PhysicalResourceId": "cloudtrail-CloudTrail-IATKD7YI5H6Y", "LogicalResourceId": "CloudTrail" }, { "StackId": "arn:aws:cloudformation:ap-northeast-1:YYYYYYYYYYYY:stack/cloudtrail/e8cd9900-6144-11ea-800c-0e8a9cd56576", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, "ResourceType": "AWS::Logs::LogGroup", "Timestamp": "2020-03-08T13:58:42.563Z", "StackName": "cloudtrail", "PhysicalResourceId": "CloudTrail/audit-log", "LogicalResourceId": "CloudTrailLogGroup" }, { "StackId": "arn:aws:cloudformation:ap-northeast-1:YYYYYYYYYYYY:stack/cloudtrail/e8cd9900-6144-11ea-800c-0e8a9cd56576", "ResourceStatus": "CREATE_COMPLETE", "DriftInformation": { "StackResourceDriftStatus": "NOT_CHECKED" }, "ResourceType": "AWS::IAM::Role", "Timestamp": "2020-03-08T13:59:01.096Z", "StackName": "cloudtrail", "PhysicalResourceId": "AWSCloudTrailLogGroupRole", "LogicalResourceId": "CloudTrailLogGroupRole" } ] }
CloudTrail以外を展開するためのサンプルテンプレートも用意されています。
https://github.com/OlafConijn/AwsOrganizationFormation/tree/master/examples
まとめ
これは神ツールですね。
アカウント数が2桁を超えるとOrganizationの管理に難儀する印象ですが、ここまでできるといろいろ捗りそうです。 いろいろ使ってガンガンフィードバックしていきたいと思った次第です。
早速、怪しい挙動を見つけたのでフィードバックしました。
Errors occur in the init command when member account names are duplicated? #41
現場からは以上です。